﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI;
using SPAdvancedSearch.Editors;
using System.Web.UI.WebControls;
using SPAdvancedSearch.Utility;
using System.Collections;

namespace SPAdvancedSearch
{
    /// <summary>
    /// Provides a ToolPart for configuring the advanced search options.
    /// </summary>
    public class AdvancedSearchToolPart : ToolPart
    {

        #region Constants

        private const string KEY_SEARCH_PROPERTIES_XML = "{71D359F0-402C-4c49-9EC9-98F096EF4C3B}";
        private const string COMMAND_EDIT_VALUES = "EditValues";

        #endregion

        #region Fields

        private GridView _propertyGrid;
        private List<SearchProperty> _searchProperties;
        private Button _addNewPropertyButton;
        private TextBox _managedPropertyNameInput;
        private TextBox _friendlyNameInput;
        private DropDownList _propertyTypeInput;
        private Hashtable _searchPropertyEditorControls;
        private Label _undefinedEditorLabel;

        #endregion

        #region Ctor

        /// <summary>
        /// Creates an instance of <see cref="AdvancedSearchToolPart"/>.
        /// </summary>
        public AdvancedSearchToolPart()
            : base()
        {
            this.Title = "Advanced Search Settings";
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the contained label for undefined editors.
        /// </summary>
        private Control UndefinedEditorLabel
        {
            get
            {
                if (_undefinedEditorLabel == null)
                {
                    _undefinedEditorLabel = new Label();
                    _undefinedEditorLabel.EnableViewState = false;
                    _undefinedEditorLabel.Text = "Additional configuration is not required for this type.";
                    _undefinedEditorLabel.CssClass = "ms-error";
                }

                return _undefinedEditorLabel;
            }
        }

        /// <summary>
        /// Gets or sets the contained search property editor controls defined by the search properties.
        /// </summary>
        private Hashtable SearchPropertyEditorControls
        {
            get
            {
                if (_searchPropertyEditorControls == null)
                {
                    _searchPropertyEditorControls = new Hashtable();
                }

                return _searchPropertyEditorControls;
            }
        }

        /// <summary>
        /// Gets the <see cref="DropDownList"/> for the type of advanced search property.
        /// </summary>
        private DropDownList PropertyTypeInput
        {
            get
            {
                if (_propertyTypeInput == null)
                {
                    _propertyTypeInput = new DropDownList();
                    _propertyTypeInput.Width = Unit.Percentage(95);
                }
                return _propertyTypeInput;
            }
        }

        /// <summary>
        /// Gets the <see cref="TextBox"/> for a new managed property.
        /// </summary>
        private TextBox ManagedPropertyNameInput
        {
            get
            {
                if (_managedPropertyNameInput == null)
                {
                    _managedPropertyNameInput = new TextBox();
                    _managedPropertyNameInput.CssClass = "ms-input";
                    _managedPropertyNameInput.Width = Unit.Percentage(95);
                }
                return _managedPropertyNameInput;
            }
        }

        /// <summary>
        /// Gets the <see cref="TextBox"/> for a friendly name.
        /// </summary>
        private TextBox FriendlyNameInput
        {
            get
            {
                if (_friendlyNameInput == null)
                {
                    _friendlyNameInput  = new TextBox();
                    _friendlyNameInput.CssClass = "ms-input";
                    _friendlyNameInput.Width = Unit.Percentage(95);
                }
                return _friendlyNameInput;
            }
        }

        /// <summary>
        /// Gets the <see cref="Button"/> for adding new properties.
        /// </summary>
        private Button AddNewButton
        {
            get
            {
                if (_addNewPropertyButton == null)
                {
                    _addNewPropertyButton = new Button();
                    _addNewPropertyButton.Text = "Add";
                    _addNewPropertyButton.CssClass = "UserButton";
                    _addNewPropertyButton.Width = Unit.Pixel(60);
                }

                return _addNewPropertyButton;
            }
        }

        /// <summary>
        /// Gets the contained <see cref="GridView"/> of search properties.
        /// </summary>
        private GridView PropertyGrid
        {
            get
            {
                if (_propertyGrid == null)
                {
                    _propertyGrid = new GridView();
                }
                return _propertyGrid;
            }
        }

        /// <summary>
        /// Gets the current <see cref="AdvancedSearchWebPart"/> being edited.
        /// </summary>
        private AdvancedSearchWebPart AdvancedSearchWebPartInstance
        {
            get { return this.WebPartToEdit as AdvancedSearchWebPart; }
        }

        /// <summary>
        /// Gets the current <see cref="AdvancedSearchWebPart"/> search properties.
        /// </summary>
        private List<SearchProperty> SearchProperties
        {
            get 
            {
                // if we have viewstate, use it
                if ((ViewState[KEY_SEARCH_PROPERTIES_XML] != null) && (_searchProperties == null))
                {
                    _searchProperties = (List<SearchProperty>)SerializationManager.HydrateObjectFromXml(typeof(List<SearchProperty>), ViewState[KEY_SEARCH_PROPERTIES_XML].ToString());
                }
                else if (_searchProperties == null) // we haven't deydrated them yet...
                {
                    _searchProperties = this.AdvancedSearchWebPartInstance.SearchProperties; 
                }
                
                return _searchProperties;

            }
        }

        /// <summary>
        /// Gets or sets the search property xml for the current <see cref="AdvancedSearchWebPart"/>.
        /// </summary>
        private string SearchPropertyXml
        {
            get 
            {
                if (ViewState[KEY_SEARCH_PROPERTIES_XML] == null)
                {
                    ViewState[KEY_SEARCH_PROPERTIES_XML] = this.AdvancedSearchWebPartInstance.SearchPropertyXml;
                }
                return ViewState[KEY_SEARCH_PROPERTIES_XML].ToString();
            }
            set 
            {
                ViewState[KEY_SEARCH_PROPERTIES_XML] = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Overridden to create the <see cref="AdvancedSearchToolPart"/>.
        /// </summary>
        protected override void CreateChildControls()
        {

            // wire-up events
            this.AddNewButton.Click += new EventHandler(AddNewPropertyButton_Click);
            this.PropertyTypeInput.Load += new EventHandler(PropertyTypeInput_Load);
            this.PropertyGrid.Load += new EventHandler(PropertyGrid_Load);
            this.PropertyGrid.RowDeleting += new GridViewDeleteEventHandler(PropertyGrid_RowDeleting);
            this.PropertyGrid.RowCommand += new GridViewCommandEventHandler(PropertyGrid_RowCommand);

            // initialize the grid
            this.InitializePropertyGrid();

            // create a panel to contain the controls
            Panel panel = new Panel();
            panel.Attributes.Add("style", "padding:3px;margin:4px;width:95%");

            // add controls

            // new property form
            panel.Controls.Add(ToolPartUtility.CreateSectionHeader("Add Property"));
            panel.Controls.Add(this.CreateNewPropertyForm());
            panel.Controls.Add(ToolPartUtility.CreateSectionSeparator());

            // existing properties grid view
            panel.Controls.Add(ToolPartUtility.CreateSectionHeader("Existing Properties"));
            panel.Controls.Add(this.PropertyGrid);

            panel.Controls.Add(ToolPartUtility.CreateSectionSeparator());

            // add property editors
            foreach (SearchProperty prop in this.SearchProperties)
            {
                EditorControlBase editorControl = prop.GetEditorControl();

                if (editorControl != null)
                {
                    // initially hide the control
                    editorControl.Visible = false;
                    editorControl.Save += new EditorControlBase.SaveHandler(editorControl_Save);
                    panel.Controls.Add(editorControl);

                    // add the editor the contained hashtable for accessibility
                    this.SearchPropertyEditorControls.Add(prop.ManagedPropertyName, editorControl);
                }
            }

            // add a label for undefined editors
            this.UndefinedEditorLabel.Visible = false;
            panel.Controls.Add(this.UndefinedEditorLabel);

            this.Controls.Add(panel);
        }

        /// <summary>
        /// Creates the new property form.
        /// </summary>
        /// <returns></returns>
        private Control CreateNewPropertyForm()
        {
            Table table = new Table();
            table.Width = Unit.Percentage(95);

            // add row for property name
            TableRow row = new TableRow();
            table.Rows.Add(row);

            TableCell cell = new TableCell();
            cell.Width = Unit.Percentage(15);
            row.Cells.Add(cell);
            cell.Text = "Property:";

            cell = new TableCell();
            cell.Width = Unit.Percentage(85);
            row.Cells.Add(cell);
            cell.Controls.Add(this.ManagedPropertyNameInput);

            // add row for label
            row = new TableRow();
            table.Rows.Add(row);

            cell = new TableCell();
            cell.Width = Unit.Percentage(15);
            row.Cells.Add(cell);
            cell.Text = "Label:";

            cell = new TableCell();
            cell.Width = Unit.Percentage(85);
            row.Cells.Add(cell);
            cell.Controls.Add(this.FriendlyNameInput);

            // add row for property display type
            row = new TableRow();
            table.Rows.Add(row);

            cell = new TableCell();
            cell.Width = Unit.Percentage(15);
            row.Cells.Add(cell);
            cell.Text = "Type:";

            cell = new TableCell();
            cell.Width = Unit.Percentage(85);
            row.Cells.Add(cell);
            cell.Controls.Add(this.PropertyTypeInput);

            // add row for button
            row = new TableRow();
            table.Rows.Add(row);

            cell = new TableCell();
            cell.ColumnSpan = 2;
            cell.HorizontalAlign = HorizontalAlign.Right;
            cell.Controls.Add(this.AddNewButton);

            row.Cells.Add(cell);

            return table;
            
        }

        /// <summary>
        /// Initializes the property grid.
        /// </summary>
        private void InitializePropertyGrid()
        {
            GridViewUtility.ApplyStyles(this.PropertyGrid);
            this.PropertyGrid.DataKeyNames = new string[] { "ManagedPropertyName" };
            this.PropertyGrid.Columns.Add(GridViewUtility.CreateDeleteCommandField());
            this.PropertyGrid.Columns.Add(GridViewUtility.CreateBoundField("Property", "ManagedPropertyName", true));
            this.PropertyGrid.Columns.Add(GridViewUtility.CreateBoundField("Label", "FriendlyName", true));
            this.PropertyGrid.Columns.Add(GridViewUtility.CreateButtonField("Type", "SearchValueType", COMMAND_EDIT_VALUES));
            
        }

        /// <summary>
        /// Binds the contained property grid to the search properties.
        /// </summary>
        private void BindPropertyGrid()
        {
            this.PropertyGrid.DataSource = this.SearchProperties;
            this.PropertyGrid.DataBind();
        }

        /// <summary>
        /// Saves the current state of the search properties.
        /// </summary>
        private void SaveSearchProperties()
        {
            this.SearchPropertyXml = SerializationManager.DehydrateObject(this.SearchProperties);
        }

        /// <summary>
        /// Overridden to apply the changes of the toolpart to the web part.
        /// </summary>
        public override void ApplyChanges()
        {
            // set the search property xml on the advanced search web part instance.
            this.AdvancedSearchWebPartInstance.SearchPropertyXml = this.SearchPropertyXml;
        }

        /// <summary>
        /// Finds a <see cref="SearchProperty"/> by key in the contained collection.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private SearchProperty FindSearchProperty(string key)
        {
            return (from p in this.SearchProperties where p.ManagedPropertyName == key select p).FirstOrDefault();
        }

        /// <summary>
        /// Hides all editor controls.
        /// </summary>
        private void HideEditorControls()
        {
            foreach (EditorControlBase editorControl in this.SearchPropertyEditorControls.Values)
            {
                editorControl.Visible = false;
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Responds to the event raised when a grid command is executed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PropertyGrid_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            // get the row and managed property key that fired the command
            int rowIndex = Convert.ToInt32(e.CommandArgument);
            string managedPropertyName = this.PropertyGrid.DataKeys[rowIndex].Value.ToString();

            // hide existing editor controls that are visible.
            this.HideEditorControls();

            SearchProperty prop = FindSearchProperty(managedPropertyName);

            // check to ensure that the managed property has an editor available.
            if (this.SearchPropertyEditorControls.ContainsKey(prop.ManagedPropertyName))
            {
                // get the editor and display it.
                EditorControlBase editor = (EditorControlBase)this.SearchPropertyEditorControls[prop.ManagedPropertyName];
                editor.Visible = true;
            }
            else
            {
                this.UndefinedEditorLabel.Visible = true;
            }

        }

        /// <summary>
        /// Responds to the event raised when the editor is saved.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void editorControl_Save(object sender, SaveEventArgs e)
        {
            // find the search property being managed and update the settings.
            SearchProperty prop = this.FindSearchProperty(e.ManagedPropertyName);
            prop.SettingsXml = e.SettingsXml;

            // save the search properties
            this.SaveSearchProperties();

            // hide the editor control
            EditorControlBase editorControl = sender as EditorControlBase;
            editorControl.Visible = false;

        }

        /// <summary>
        /// Responds to the event raised when the property type input is loaded.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PropertyTypeInput_Load(object sender, EventArgs e)
        {
            if (_propertyTypeInput.Items.Count == 0)
            {
                _propertyTypeInput.DataSource = Enum.GetNames(typeof(SearchPropertyValueType));
                _propertyTypeInput.DataBind();
            }
        }

        /// <summary>
        /// Responds to the event raised when the PropertyGrid is loaded.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PropertyGrid_Load(object sender, EventArgs e)
        {
            if (this.PropertyGrid.Rows.Count == 0)
            {
                BindPropertyGrid();
            }
        }

        /// <summary>
        /// Responds to the event raised when a PropertyGrid row is being deleted.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PropertyGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
        {

            string key = this.PropertyGrid.DataKeys[e.RowIndex].Value.ToString();
            SearchProperty prop = FindSearchProperty(key);

            if (prop != null)
            {
                this.SearchProperties.Remove(prop);
                this.SaveSearchProperties();
                this.BindPropertyGrid();
            }
        }

        /// <summary>
        /// Responds to the event raised when the add new property button is clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AddNewPropertyButton_Click(object sender, EventArgs e)
        {
            //TODO: report error if required fields aren't entered...
            if ( (!string.IsNullOrEmpty(this.ManagedPropertyNameInput.Text)) && (!string.IsNullOrEmpty(this.FriendlyNameInput.Text)) )
            {
                // create new property
                SearchProperty prop = new SearchProperty();

                prop.FriendlyName = this.FriendlyNameInput.Text;
                prop.ManagedPropertyName = this.ManagedPropertyNameInput.Text;
                prop.SearchValueType = (SearchPropertyValueType)Enum.Parse(typeof(SearchPropertyValueType), this.PropertyTypeInput.SelectedValue, true);

                // save new property
                this.SearchProperties.Add(prop);
                this.SaveSearchProperties();

                this.BindPropertyGrid();

                // clear form inputs
                this.PropertyTypeInput.SelectedIndex = 0;
                this.FriendlyNameInput.Text = string.Empty;
                this.ManagedPropertyNameInput.Text = string.Empty;
            }
        }

        #endregion

    }
}
